/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using Microsoft.Scripting.Ast;
using Microsoft.Scripting.Runtime;

namespace Microsoft.Scripting.Generation {

    /// <summary>
    /// Rewrites globals to static fields on a type
    /// TODO: move to Microsoft.Scripting
    /// </summary>
    internal sealed class GlobalStaticFieldRewriter : GlobalOptimizedRewriter {
        private readonly TypeGen _typeGen;
        private readonly Dictionary<GlobalVariableExpression, FieldBuilder> _fields = new Dictionary<GlobalVariableExpression, FieldBuilder>();
        private FieldBuilder _contextField;

        internal GlobalStaticFieldRewriter(TypeGen typeGen) {
            _typeGen = typeGen;
        }

        protected override Expression MakeWrapper(GlobalVariableExpression variable) {
            Debug.Assert(!_fields.ContainsKey(variable));

            FieldBuilder field = _typeGen.TypeBuilder.DefineField(
                variable.Name,
                typeof(ModuleGlobalWrapper),
                FieldAttributes.Assembly | FieldAttributes.Static
            );

            _fields.Add(variable, field);

            return Expression.Field(null, field);
        }

        internal void EmitDictionary() {
            // TODO: Force all dictionaries to share same object data (for multi-module)

            _contextField = _typeGen.TypeBuilder.DefineField(
                CodeContext.ContextFieldName,
                typeof(CodeContext),
                FieldAttributes.Public | FieldAttributes.Static
            );

            MakeGetMethod();
            MakeSetMethod();
            MakeRawKeysMethod();
            MakeInitialization();
        }

        #region CreateDictionary implementation

        private void MakeInitialization() {
            _typeGen.TypeBuilder.AddInterfaceImplementation(typeof(IModuleDictionaryInitialization));
            MethodInfo baseMethod = typeof(IModuleDictionaryInitialization).GetMethod("InitializeModuleDictionary");
            ILGen cg = _typeGen.DefineExplicitInterfaceImplementation(baseMethod);

            Label ok = cg.DefineLabel();
            cg.EmitFieldGet(_contextField);
            cg.Emit(OpCodes.Ldnull);
            cg.Emit(OpCodes.Ceq);
            cg.Emit(OpCodes.Brtrue_S, ok);
            cg.EmitNew(typeof(InvalidOperationException), Type.EmptyTypes);
            cg.Emit(OpCodes.Throw);
            cg.MarkLabel(ok);

            // arg0 -> this
            // arg1 -> MyModuleDictType.ContextSlot
            cg.EmitLoadArg(1);
            cg.EmitFieldSet(_contextField);

            ConstructorInfo wrapperCtor = typeof(ModuleGlobalWrapper).GetConstructor(new Type[] { typeof(CodeContext), typeof(SymbolId) });
            foreach (KeyValuePair<GlobalVariableExpression, FieldBuilder> kv in _fields) {
                // wrapper = new ModuleGlobalWrapper(context, name);
                cg.EmitLoadArg(1);
                cg.EmitSymbolId(SymbolTable.StringToId(kv.Key.Name));
                cg.Emit(OpCodes.Newobj, wrapperCtor);
                cg.Emit(OpCodes.Stsfld, kv.Value);
            }

            cg.Emit(OpCodes.Ret);
        }

        //
        // This generates a method like the following:
        //
        //  TryGetExtraValue(int name, object out value) {
        //      if (name1 == name) {
        //          value = type.name1Slot.RawValue;
        //          return value != Uninitialized.Instance;
        //      }
        //      if (name2 == name) {
        //          value = type.name2Slot.RawValue;
        //          return value != Uninitialized.Instance;
        //      }
        //      ...
        //      return false
        //  }

        private void MakeGetMethod() {
            MethodInfo baseMethod = typeof(CustomSymbolDictionary).GetMethod("TryGetExtraValue", BindingFlags.NonPublic | BindingFlags.Instance);
            ILGen cg = _typeGen.DefineMethodOverride(baseMethod);

            foreach (KeyValuePair<GlobalVariableExpression, FieldBuilder> kv in _fields) {
                SymbolId name = SymbolTable.StringToId(kv.Key.Name);

                cg.EmitSymbolId(name);
                // arg0 -> this
                cg.EmitLoadArg(1);
                cg.EmitCall(typeof(SymbolId), "op_Equality");

                Label next = cg.DefineLabel();
                cg.Emit(OpCodes.Brfalse_S, next);

                cg.EmitLoadArg(2);

                // Expects to push as an object.
                EmitGetRawFromObject(cg, kv.Value);
                cg.Emit(OpCodes.Stind_Ref);

                EmitGetRawFromObject(cg, kv.Value);
                cg.EmitUninitialized();
                cg.Emit(OpCodes.Ceq);
                cg.Emit(OpCodes.Not);
                cg.Emit(OpCodes.Ret);
                cg.MarkLabel(next);
            }
            cg.EmitInt(0);
            cg.Emit(OpCodes.Ret);
        }

        private static void EmitGetRawFromObject(ILGen cg, FieldBuilder wrapper) {
            cg.EmitFieldGet(wrapper);
            cg.EmitPropertyGet(typeof(ModuleGlobalWrapper), "RawValue");
        }

        // This generates a method like the following:
        //
        //  TrySetExtraValue(object name, object value) {
        //      if (name1 == name) {
        //          type.name1Slot = value;
        //          return 1;
        //      }
        //      if (name2 == name) {
        //          type.name2Slot = value;
        //          return 1;
        //      }
        //      ...
        //      return 0
        //  }

        private void MakeSetMethod() {
            MethodInfo baseMethod = typeof(CustomSymbolDictionary).GetMethod("TrySetExtraValue", BindingFlags.NonPublic | BindingFlags.Instance);
            ILGen cg = _typeGen.DefineMethodOverride(baseMethod);
            Slot valueSlot = new ArgSlot(2, typeof(object), cg);

            foreach (KeyValuePair<GlobalVariableExpression, FieldBuilder> kv in _fields) {
                SymbolId name = SymbolTable.StringToId(kv.Key.Name);

                cg.EmitSymbolId(name);
                // arg0 -> this
                cg.EmitLoadArg(1);
                cg.EmitCall(typeof(SymbolId), "op_Equality");

                Label next = cg.DefineLabel();
                cg.Emit(OpCodes.Brfalse_S, next);

                EmitSetRawFromObject(cg, kv.Value, valueSlot);

                cg.EmitInt(1);
                cg.Emit(OpCodes.Ret);
                cg.MarkLabel(next);
            }
            cg.EmitInt(0);
            cg.Emit(OpCodes.Ret);
        }

        private static void EmitSetRawFromObject(ILGen cg, FieldBuilder wrapper, Slot value) {
            cg.EmitFieldGet(wrapper);
            value.EmitGet(cg);
            cg.EmitPropertySet(typeof(ModuleGlobalWrapper), "CurrentValue");
        }

        private ILGen/*!*/ MakeRawKeysMethod() {
            Slot rawKeysCache = _typeGen.AddStaticField(typeof(SymbolId[]), "ExtraKeysCache");
            ILGen init = _typeGen.TypeInitializer;

            init.EmitInt(_fields.Count);
            init.Emit(OpCodes.Newarr, typeof(SymbolId));

            int current = 0;
            foreach (GlobalVariableExpression variable in _fields.Keys) {
                init.Emit(OpCodes.Dup);
                init.EmitInt(current++);
                init.EmitSymbolId(SymbolTable.StringToId(variable.Name));
                init.EmitStoreElement(typeof(SymbolId));
            }

            rawKeysCache.EmitSet(init);

            MethodInfo baseMethod = typeof(CustomSymbolDictionary).GetMethod("GetExtraKeys", BindingFlags.Public | BindingFlags.Instance);
            ILGen cg = _typeGen.DefineExplicitInterfaceImplementation(baseMethod);

            rawKeysCache.EmitGet(cg);
            cg.Emit(OpCodes.Ret);
            return cg;
        }

        #endregion
    }
}
